package net.qiqbframework.modelling.command;


import lombok.Getter;
import net.qiqbframework.commandhandling.*;
import net.qiqbframework.common.Registration;
import net.qiqbframework.common.utils.ReflectionUtil;
import net.qiqbframework.messaging.annotation.MessageExecutableHandler;
import net.qiqbframework.messaging.task.CurrentMessageTask;
import net.qiqbframework.modelling.command.voucher.LoadVoucher;
import net.qiqbframework.modelling.domain.Aggregate;
import net.qiqbframework.modelling.domain.AggregateModel;
import net.qiqbframework.modelling.domain.AggregateNotFoundException;
import net.qiqbframework.modelling.domain.BizIdentifierModel;
import net.qiqbframework.modelling.repository.AggregateBizIdentifierVoucher;
import net.qiqbframework.modelling.repository.Repository;
import net.qiqbframework.serialization.SerializedObject;
import net.qiqbframework.serialization.Serializer;

import java.lang.reflect.Executable;
import java.util.*;
import java.util.stream.Collectors;

public class AggregateAnnotationCommandHandler<T> implements CommandMessageHandler {

    private final Repository<T> repository;

    private final CommandTargetResolver commandTargetResolver;

    private final Map<String, Set<CommandMessageHandler>> supportedCommandsByName;

    @Getter
    private final AggregateModel<T> aggregateModel;


    private final AggregateHandlingMonitor aggregateHandlingMonitor;

    private final Serializer aggregateRootSerializer;

    protected AggregateAnnotationCommandHandler(AggregateAnnotationCommandHandlerBuilder<T> builder) {

        builder.validate();

        this.repository = builder.repository;
        this.aggregateHandlingMonitor = builder.aggregateHandlingMonitor();
        this.aggregateRootSerializer = builder.aggregateRootSerializer();
        this.commandTargetResolver = builder.commandTargetResolver;
        this.supportedCommandsByName = new HashMap<>();
        AggregateModel<T> aggregateModel = builder.buildAggregateModel();
        this.aggregateModel = aggregateModel;
        initializeHandlers(aggregateModel);

    }

    public Registration subscribe(CommandBus commandBus) {
        List<Registration> subscriptions = supportedCommandsByName.entrySet().stream()
                // flatMap
                .flatMap(entry -> entry.getValue().stream().map(messageHandler -> commandBus.subscribe(entry.getKey(), this)))
                // filter
                .filter(Objects::nonNull).toList();
        return () -> subscriptions.stream().map(Registration::cancel).reduce(Boolean::logicalOr).orElse(false);
    }

    private void initializeHandlers(AggregateModel<T> aggregateModel) {

        aggregateModel.allCommandHandlers().values().stream().flatMap(List::stream)
                // collect
                .collect(Collectors.groupingBy(this::getHandlerSignature))
                //
                .forEach((signature, commandHandlers) -> initializeHandler(aggregateModel, commandHandlers.get(0)));


    }

    private String getHandlerSignature(MessageExecutableHandler handler) {
        return handler.unwrap(Executable.class).map(ReflectionUtil::toDiscernibleSignature).orElseThrow(() -> new IllegalStateException("A handler is missing an Executable. Please provide an " + "Executable in your MessageHandlingMembers"));
    }

    private void initializeHandler(AggregateModel<T> aggregateModel, MessageExecutableHandler handler) {

        handler.unwrap(CommandMessageExecutableHandler.class).ifPresent(cmh -> {
            CommandMessageHandler messageHandler;
            if (cmh.isFactoryHandler()) {
                messageHandler = new AggregateConstructorCommandHandler(handler);
            } else {
                messageHandler = new AggregateCommandHandler(cmh);
            }
            supportedCommandsByName.computeIfAbsent(cmh.commandNaming(), key -> new HashSet<>()).add(messageHandler);
        });
    }

    @Override
    public Object handle(CommandMessage<?> commandMessage) throws Exception {
        final Set<CommandMessageHandler> commandMessageHandlers = supportedCommandsByName.get(commandMessage.getCommandName());
        if (commandMessageHandlers == null || commandMessageHandlers.isEmpty()) {
            throw new CommandHandlerNotFoundException(commandMessage);
        }
        return commandMessageHandlers.stream().findFirst().get().handle(commandMessage);
    }


    protected Object resolveReturnValue(@SuppressWarnings("unused") CommandMessage<?> command, Aggregate<T> createdAggregate) {
        return createdAggregate.identifier();
    }


    private class AggregateConstructorCommandHandler implements CommandMessageHandler {

        private final MessageExecutableHandler handler;

        public AggregateConstructorCommandHandler(MessageExecutableHandler handler) {
            this.handler = handler;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Object handle(CommandMessage<?> command) throws Exception {
            aggregateHandlingMonitor.beforeHandle(command, null);
            Aggregate<T> aggregate = repository.newInstance(() -> (T) handler.handle(command, null));

            final SerializedObject<byte[]> afterHandleSerialize = aggregateRootSerializer.serialize(aggregate.aggregateRoot(), byte[].class);
            final T afterHandlSnapshot = aggregateRootSerializer.deserialize(afterHandleSerialize);
            // 添加快照
            CurrentMessageTask.get().afterCommit((t) -> {
                aggregateHandlingMonitor.afterCommit(command, null, afterHandlSnapshot);
            });
            return resolveReturnValue(command, aggregate);
        }
    }

    private class AggregateCommandHandler implements CommandMessageHandler {

        private final CommandMessageExecutableHandler handler;

        public AggregateCommandHandler(CommandMessageExecutableHandler handler) {
            this.handler = handler;
        }

        @Override
        public Object handle(CommandMessage<?> command) throws Exception {
            final Object commands = CurrentMessageTask.get().getResource("_commands");
            // 尝试从缓存中加载，如果有直接拿
            AggregateBizIdentifierVoucher thisBizIdentifierVoucher = null;
            net.qiqbframework.modelling.command.voucher.LoadVoucher thisLoadVoucher = null;
            // LoadMessage<?, T> loadMessage = new GenericLoadMessage<>(target, annotatedBizIdentifier.getModel().getModelName(), aggregateModel);

            final ArrayList<AggregateBizIdentifierVoucher> bizIdentifierVouchers = new ArrayList<>();
            if (commands instanceof List<?> list) {

                for (Object c : list.subList(list.indexOf(command), list.size())) {

                    net.qiqbframework.modelling.command.voucher.LoadVoucher loadVoucher = commandTargetResolver.resolveLoadVoucher((CommandMessage<?>) c);
                    if (c == command) {
                        thisLoadVoucher = loadVoucher;
                    }
                    final Optional<BizIdentifierVoucherMember> primaryBizIdentifierMember = loadVoucher.getPrimaryBizIdentifierMember();
                    if (primaryBizIdentifierMember.isPresent()) {
                        // 通过命令获取ID和version 加载凭证
                        final BizIdentifierVoucherMember bizIdentifierMember = primaryBizIdentifierMember.get();
                        String businessIdName = bizIdentifierMember.getBizIdentifierName();
                        final BizIdentifierModel<?> businessIdModel = aggregateModel.getBizIdentifierModel(businessIdName);
                        if (businessIdModel == null) {
                            throw new IllegalArgumentException("聚合类[%s]中未定义属性名为[%s]的业务ID。".formatted(aggregateModel.entityClass(), businessIdName));
                        }
                        final AggregateBizIdentifierVoucher bizIdentifierVoucher = new AggregateBizIdentifierVoucher(businessIdName, bizIdentifierMember.getValue());
                        bizIdentifierVoucher.setExpectedVersion(loadVoucher.getVersion());
                        if (c == command) {
                            thisBizIdentifierVoucher = bizIdentifierVoucher;
                        }
                        bizIdentifierVouchers.add(bizIdentifierVoucher);
                        if (repository.loadFromCache(bizIdentifierVoucher) != null) {
                            // 如果从缓存中加载到数据，无需加载
                            break;
                        }
                    } else {
                        // 尝试从之前的数据
                        throw new CommandExecutionException("命令[%s]必须是需要加载凭证".formatted(command.getCommandName()));
                    }

                }
            } else {
                LoadVoucher loadVoucher = commandTargetResolver.resolveLoadVoucher(command);
                thisLoadVoucher = loadVoucher;
                final Optional<BizIdentifierVoucherMember> primaryBizIdentifierMember = loadVoucher.getPrimaryBizIdentifierMember();
                if (primaryBizIdentifierMember.isPresent()) {
                    // 通过命令获取ID和version 加载凭证
                    final BizIdentifierVoucherMember bizIdentifierMember = primaryBizIdentifierMember.get();
                    String businessIdName = bizIdentifierMember.getBizIdentifierName();
                    final BizIdentifierModel<?> businessIdModel = aggregateModel.getBizIdentifierModel(businessIdName);
                    if (businessIdModel == null) {
                        throw new IllegalArgumentException("聚合类[%s]中未定义属性名为[%s]的业务ID。".formatted(aggregateModel.entityClass(), businessIdName));
                    }
                    final AggregateBizIdentifierVoucher bizIdentifierVoucher = new AggregateBizIdentifierVoucher(businessIdName, bizIdentifierMember.getValue());
                    bizIdentifierVoucher.setExpectedVersion(loadVoucher.getVersion());
                    thisBizIdentifierVoucher = bizIdentifierVoucher;
                    bizIdentifierVouchers.add(bizIdentifierVoucher);
                } else {
                    // 尝试从之前的数据
                    throw new CommandExecutionException("命令[%s]必须是需要加载凭证".formatted(command.getCommandName()));
                }
            }
            if (thisBizIdentifierVoucher == null) {
                throw new IllegalArgumentException("找不到业务ID加载器");
            }
            final Map<AggregateBizIdentifierVoucher, Aggregate<T>> bizIdentifierVoucherAggregateMap = repository.batchLoad(bizIdentifierVouchers);
            final Aggregate<T> aggregate = bizIdentifierVoucherAggregateMap.get(thisBizIdentifierVoucher);


            if (aggregate == null) {
                throw new AggregateNotFoundException(thisBizIdentifierVoucher.getKey(), "根据业务ID找不到对应的聚合对象");
            }
            // todo 需要验证所有加载凭证是否匹配，如果不匹配，将报错
            final Map<BizIdentifierModel<?>, Object> bizIdentifiers = aggregateModel.getBizIdentifiers(aggregate.aggregateRoot());

            for (BizIdentifierVoucherMember subBizIdentifierMember : thisLoadVoucher.listSubBizIdentifierMembers()) {
                final Object commandSubBizId = subBizIdentifierMember.getValue();
                final String commandSubBizIdName = subBizIdentifierMember.getBizIdentifierName();
                if (!commandSubBizId.equals(bizIdentifiers.get(aggregateModel.getBizIdentifierModel(commandSubBizIdName)))) {
                    throw new IllegalArgumentException("业务ID不匹配");
                }
            }
            // 进行虚拟化
            final SerializedObject<byte[]> beforeHandleSerialize = aggregateRootSerializer.serialize(aggregate.aggregateRoot(), byte[].class);
            final T beforeHandleSnapshot = aggregateRootSerializer.deserialize(beforeHandleSerialize);
            aggregateHandlingMonitor.beforeHandle(command, beforeHandleSnapshot);
            final Object result = aggregate.handle(command);
            final SerializedObject<byte[]> afterHandleSerialize = aggregateRootSerializer.serialize(aggregate.aggregateRoot(), byte[].class);
            final T afterHandlSnapshot = aggregateRootSerializer.deserialize(afterHandleSerialize);
            // 添加快照
            CurrentMessageTask.get().afterCommit((t) -> {
                aggregateHandlingMonitor.afterCommit(command, beforeHandleSnapshot, afterHandlSnapshot);
            });
            return result;
        }


    }
}
